int issue_prefetch_request(unsigned long long int addr, int bsize, 
						   int sid, int wid, enum mem_access_type mem_acc, address_type pc)
{ 
   mem_fetch_t *mf;
   mem_fetch_t *prefetch_access;

   mf = (mem_fetch_t*) calloc(1,sizeof(mem_fetch_t));
   mf->request_uid = g_next_request_uid++;
   mf->addr = addr;
   mf->nbytes_L1 = bsize;
   mf->sid = sid;
   mf->source_node = sid / gpu_concentration;
   mf->wid = wid; // is it correct? -- will it make proper notification to waiting warps or done by mshrs?
   mf->cache_hits_waiting = 0;
   mf->txbytes_L1 = 0;
   mf->rxbytes_L1 = 0;  
   mf->mshr = NULL;
   mf->write = false;
   mf->isprefetched = true;
   mf->there_was_demand = false;
   mf->type = RD_REQ; 
   mf->which_priority = 1; // second group -- prefetch group -- Medium
   
   memlatstat_start(mf); 
   
   addrdec_tlx(addr,&mf->tlx);
   mf->bank = mf->tlx.bk;
   mf->chip = mf->tlx.chip;
   if (gpgpu_cache_dl2_opt)
      mf->nbytes_L2 = L2c_get_linesize( dram[mf->tlx.chip] );
   else
      mf->nbytes_L2 = 0;
   mf->txbytes_L2 = 0;
   mf->rxbytes_L2 = 0;  

   mf->write_mask = NO_PARTIAL_WRITE;
  
   mf->mem_acc = mem_acc;
   mf->pc = pc;
   mf->sent_time = 0;
  

  
   switch (mem_acc) {
   case CONST_ACC_R: gpgpu_n_mem_const_prefetch++; break; // should be 0
   case TEXTURE_ACC_R: gpgpu_n_mem_texture_prefetch++; break; // should be 0
   case GLOBAL_ACC_R: gpgpu_n_mem_read_global_prefetch++; break; 
   case GLOBAL_ACC_W: gpgpu_n_mem_write_global_prefetch++; break; // should be 0
   case LOCAL_ACC_R: gpgpu_n_mem_read_local_prefetch++; break;
   case LOCAL_ACC_W: gpgpu_n_mem_write_local_prefetch++; break; // should be 0
   default: assert(0);
   } 
	
	// GLOBAL_ACC_R + LOCAL_ACC_R = Total Prefetches Sent
	
    prefetch_process_requests[sid].push_back(mf); // prefetch processing queue

    while(!prefetch_process_requests[sid].empty()) {
		
    prefetch_access = prefetch_process_requests[sid].back();	
	 
    int rsize = bsize;
 
		if (!mf->write) {
			rsize = READ_PACKET_SIZE;
		}
    
		if(!check_icnt_has_buffer(&prefetch_access->addr, &rsize, 1, sid)) {
        		gpu_stall_sh2icnt_prefetch++;
         		return -1;
		}
        
		issue_mf_from_fq(prefetch_access); // send prefetch request
		if (((mem_acc == GLOBAL_ACC_R) || (mem_acc == LOCAL_ACC_R))) {
			mf->sent_time = gpu_sim_cycle;		
			if ((sid == gpgpu_trace_core) && gpgpu_trace_option) { 
				 printf("PREFETCH:");
				 printf("PC[%08x] ", pc); 
	    	     printf("address[0x%llx] ", addr);
				 printf("wid = %d ", mf->wid); // does it matter?
				 printf("cycle= %lld ", gpu_sim_cycle);
				 printf("ctaid= %d ", sc[sid]->thread[wid*32].cta_id);
				 printf("distance=%d ", find_distance(addr, 1));
				 printf("mem_cntrll=%d ", find_mem(addr));
				 printf("Bank=%x ", mf->tlx.bk);
				 printf("Chip=%x ", mf->tlx.chip);
				 printf("Row=%x ", mf->tlx.row);
				 printf("Column=%x ", mf->tlx.col);
				 printf("current_net_lat=%lld ", gpgpu_mytotal_latency);
                 printf("\n");
			}
		}
		//printf("I am Prefetching = %x\n", prefetch_access->addr);
		gpgpu_prefetch_init++; // prefetch initiated
		prefetch_sent_requests[sid].push_back(prefetch_access); 
		prefetch_process_requests[sid].pop_back(); // remove from prefetch processing queue
    
   }

	return 0;

}

/* void printGSTable(int sid)
{
	for(int i = 0; i< GS_table[sid].GS_entries.size(); i++)
	{
		printf("\n Core %d;;; GS Entry : %d ",sid, i);
		printf("\n Stride:%d   ;;;  end_addr:%x   ;;;  wid:%d  ;;;  pc:%x",\
							GS_table[sid].GS_entries.at(i).stride,\
							GS_table[sid].GS_entries.at(i).end_addr,\
							GS_table[sid].GS_entries.at(i).wid,\
							GS_table[sid].GS_entries.at(i).pc);		
	}
} */
 
/* void printPWSTable(int sid)
{
	for(int i = 0; i< (int) PWS_table[sid].PWS_entries.size(); i++)
	{
		printf("\n Core %d;;; PWS Entry : %d ",sid, i);
	    printf("\n PWS Entry : %d  ;;;  SW_Stride:%d   ;;;  end_addr:%x   ;;;  wid:%d  ;;;  pc:%x" , 
							i,\
							PWS_table[sid].PWS_entries.at(i).SW_stride,\
							PWS_table[sid].PWS_entries.at(i).end_addr,\
							PWS_table[sid].PWS_entries.at(i).wid,\
							PWS_table[sid].PWS_entries.at(i).pc);	
	}
} */

/* void printIPTable(int sid)
{
	printf("\n PRINT iP TAblE : Core %d;;; ",sid);
	for(int i = 0; i< (int) IP_table[sid].IP_entries.size(); i++)
	{		
		printf("\n IP Entry : %d  ;;;  IW_Stride:%d   ;;;  SW_Stride:%d   ;;;  end_addr:%x   ;;;  wid:%d  ;;;  pc:%x   ;;;  counter:%d", 
							i,\
							IP_table[sid].IP_entries.at(i).IW_stride,\
							IP_table[sid].IP_entries.at(i).SW_stride,\
							IP_table[sid].IP_entries.at(i).end_addr,\
							IP_table[sid].IP_entries.at(i).wid,\
							IP_table[sid].IP_entries.at(i).pc,\
							IP_table[sid].IP_entries.at(i).counter);		
	}
} */

// store inter-warp stride (index into this) -- trained table
int check_AD_table(mem_fetch_t *mf)
{
	//printf("\n GS Table for this core is : \n ");
	//printGSTable(sid);
	unsigned long long int my_AD_stride = 0; 
	address_type pc   = mf->pc;
	//int wid  = mf->wid;
	int sid  = mf->sid;
	//unsigned long long int addr = mf->addr;

	
	for(int i = 0; i< (int) AD_table[sid].AD_entries.size(); i++)
	{
		if(AD_table[sid].AD_entries.at(i).pc == pc)
		{
		//printf("\n 2. Found in GS Table. Returning : %x",addr + AD_table[sid].AD_entries.at(i).stride);
				if (AD_table[sid].AD_entries.at(i).counter > 2) {
								my_AD_stride = AD_table[sid].AD_entries.at(i).IW_stride;
								// after IW stride is found for some pc delete all entries for that pc
							    for(int k = 0; k< (int) IP_table[sid].IP_entries.size();k++)
								{
									if(IP_table[sid].IP_entries.at(k).pc == pc) {
									IP_table[sid].IP_entries.erase(IP_table[sid].IP_entries.begin() + k);
									}
								}
								
				}	
		}	
	}
	
	// Table = 64, LRU, Remove the entry with min count 
	if (AD_table[sid].AD_entries.size() >= 64) {
					long int min_count = AD_table[sid].AD_entries.at(0).counter;
					int erase_me = 0 ;
					for(int i =  ((int) AD_table[sid].AD_entries.size() -1 ); i >= 0;i--) { // list sorted (age-based)
						if (min_count > AD_table[sid].AD_entries.at(i).counter) {
						min_count =  AD_table[sid].AD_entries.at(i).counter;
						erase_me = i;
						}
					}	
					if(erase_me) {
						AD_table[sid].AD_entries.erase(AD_table[sid].AD_entries.begin() + erase_me);
						//printf("now erasing");
					}
	}


	return 0;
}


//IP (Inter Warp Prefetching)	 -- Training table
int check_IP_table(mem_fetch_t *mf)
{
	//printf("\n 4. In check_ip table. Printing the table first.");
	//printIPTable(sid);	

	//printf("\n Address got :%x",addr);
	int present_ip = 0;
	int present_ad = 0;
	int control = find_mem(mf->addr);
	int bank = mf->tlx.bk;
	int chip = mf->tlx.chip;
	int row  = mf->tlx.row;
	int col  = mf->tlx.col;
	address_type pc   = mf->pc;
	int wid  = mf->wid;
	int sid  = mf->sid;
	unsigned long long int addr = mf->addr;
	IP_entry_t *new_IP_entry = NULL;  
 
	
	for(int i = 0; i< (int) IP_table[sid].IP_entries.size();i++)
	{
		if(IP_table[sid].IP_entries.at(i).pc == pc)
		{		
						for(int j = 0; i< (int) IP_table[sid].IP_entries.size();i++) {
								if (IP_table[sid].IP_entries.at(j).row == row && IP_table[sid].IP_entries.at(j).bank == bank && 
									IP_table[sid].IP_entries.at(j).chip == chip && IP_table[sid].IP_entries.at(j).control == control) {
									present_ip = 1;                     
									IP_table[sid].IP_entries.push_back(IP_entry_t());
									new_IP_entry = &IP_table[sid].IP_entries.back();
		
									new_IP_entry->pc = pc;
									new_IP_entry->wid = wid;
									new_IP_entry->end_addr = addr;
									new_IP_entry->IW_stride = (addr - IP_table[sid].IP_entries.at(j).end_addr) / abs(wid - IP_table[sid].IP_entries.at(j).wid);
									new_IP_entry->row = row;
									new_IP_entry->col = col;
									new_IP_entry->chip = chip;
									new_IP_entry->control = control;
									new_IP_entry->bank = bank;
								}
						}
						
						if (sid == 1) {
								//printf("found warp\n");
								//printf("pc=%x, wid=%d, addr=%x \n", pc, wid, addr);
						}
	       				
  					        
						  for(int j = 0; j< (int) AD_table[sid].AD_entries.size(); j++)
						  {
								if(AD_table[sid].AD_entries.at(j).pc == pc)
								{
										 present_ad = 1;
										 if (AD_table[sid].AD_entries.at(j).IW_stride ==  new_IP_entry->IW_stride) {
											 AD_table[sid].AD_entries.at(j).counter++; 
										 }
										 else {	
											  AD_table[sid].AD_entries.at(j).IW_stride = new_IP_entry->IW_stride;
											  AD_table[sid].AD_entries.at(j).IW_stride = new_IP_entry->wid;
											  AD_table[sid].AD_entries.at(j).counter = 1;
										 }
								}
						   }
						   
						   if (present_ad == 0) {
								AD_entry_t *new_AD_entry;                       
								AD_table[sid].AD_entries.push_back(AD_entry_t());
								new_AD_entry = &AD_table[sid].AD_entries.back();
								new_AD_entry->pc = pc;
								new_AD_entry->counter = 1;
						   }	
				return 0;
		   }
	 }

			if(present_ip == 0)
			{
					if (sid == 1) {
                    //printf("\n Putting new entry in IP table");
					}                  
        	        IP_table[sid].IP_entries.push_back(IP_entry_t());
                	new_IP_entry = &IP_table[sid].IP_entries.back();

                	new_IP_entry->pc = pc;
                	new_IP_entry->wid = wid;
               	 	new_IP_entry->end_addr = addr;
                	new_IP_entry->IW_stride = 0;
					new_IP_entry->row = row;
					new_IP_entry->col = col;
					new_IP_entry->chip = chip;
					new_IP_entry->control = control;
					new_IP_entry->bank = bank;
					if (sid == 1) {
					//printf("new:pc=%x, new:wid=%d, new:addr=%x, new:iwstride=%d \n", pc, wid, addr, new_IP_entry->IW_stride);
					}
			}

   return 0;
}




bool check_in_prefetch_queues(mem_fetch_t* mf, bool from_degree, unsigned long long int addr) {
   //check in already sent prefetches
		// check in sent queue
		for(int i=0; i < (int)prefetch_sent_requests[mf->sid].size(); i++) {
				  if(prefetch_sent_requests[mf->sid].at(i)->addr == addr) {
							    if (from_degree) {
									return true;
								}
								else if(prefetch_sent_requests[mf->sid].at(i)->there_was_demand == false) {
								//gpgpu_prefetch_late++;
								// add code for distribution
								//prefetch_sent_requests[sid].at(i)->mshr = mshr;
								prefetch_sent_requests[mf->sid].at(i) = mf;
								prefetch_sent_requests[mf->sid].at(i)->there_was_demand = true;
								prefetch_sent_requests[mf->sid].at(i)->write = false;
								prefetch_sent_requests[mf->sid].at(i)->isprefetched = true;
							    //printf("yes:, there is demand for (sent) = %x, coreid = %d\n", mf->addr, mf->sid);
							    //printf("yes:, are you there? %x, coreid = %d\n", prefetch_sent_requests[mf->sid].at(i)->addr, mf->sid);
								return true;
								}								
	
					}
		}

		// check in process queue
		for(int i=0; i < (int)prefetch_process_requests[mf->sid].size(); i++) {
				  if(prefetch_process_requests[mf->sid].at(i)->addr == addr) {			
								if (from_degree) {
									return true;
								}
								else if (prefetch_process_requests[mf->sid].at(i)->there_was_demand == false) {
								//gpgpu_prefetch_late++;
								// add code for distribution
								//prefetch_process_requests[sid].at(i)->mshr = mshr;
								prefetch_process_requests[mf->sid].at(i) = mf;
								prefetch_process_requests[mf->sid].at(i)->there_was_demand = true;
								prefetch_process_requests[mf->sid].at(i)->write = false;
								prefetch_process_requests[mf->sid].at(i)->isprefetched = true;
								//printf("yes:, there is demand for (process) = %x\n", mf->addr);
								return true;
								}
                   }
        }
	return false;
}




//int cta_size = ptx_sim_cta_size();
//(gpgpu_shader_cta * cta_size) / warp_size;
 
 
void collect_trace(int sid, address_type pc, unsigned long long int addr, int wid) {
   //TRACE Printing from core side (only for data cache)
	FILE *trace_file;
	trace_file = fopen("trace.txt", "a");
	if (sid == gpgpu_trace_core) { 
			 fprintf(trace_file,"PC[%08x] ", pc); 
			 fprintf(trace_file,"address[0x%llx] ", addr);
			 fprintf(trace_file,"wid = %d ", wid);
			 fprintf(trace_file,"coreid = %d ", sid);
			 fprintf(trace_file,"cycle= %lld ", gpu_sim_cycle);
			 fprintf(trace_file,"\n");
    }
   	fclose(trace_file);
}

int monitor_demands(mem_fetch_t *mf) {
	   bool issue_prefetch = false; 
	   bool no_prefetch = true;

	   for (int i = 0; i < 32; i++) {
			to_be_prefetched[i] = -1; 
	   }
    
        check_IP_table(mf); // add training entry (inter);
		check_AD_table(mf); // prefetch these blocks (to_be_prefetched  is updated here)
	
	    for(int j = 0; j < 32; j++) {  
			if (to_be_prefetched[j] != -1) { 
				 no_prefetch = false; // there is some entry to prefetched
			}
   		}

		if(no_prefetch) {
				issue_mf_from_fq(mf); // send demand request in the network (now)
				gpgpu_demand_init++;  // prefetch is NOT following
				return 0;
		}
	
		issue_mf_from_fq(mf); // send demand request in the network (now) -- prefetch is also following
		gpgpu_demand_init++;  // demand sent when prefetcher is on
	 
		for(int j = 0; j < 32; j++) {  
		
		  if(to_be_prefetched[j] != -1) {	  
		
			bool check_me_prefetch = false;
			// see if this address is already prefetched?
			check_me_prefetch = check_in_prefetch_queues(mf, true, to_be_prefetched[j] );
			
			bool in_mshr = false; // by default a network request
			mshr_entry_t* check_mshr = NULL;	    
	    
			// see if already a demand request has been sent
			check_mshr = sc[mf->sid]->mshr_unit->m_mshr_lookup.lookup(to_be_prefetched[j]);	
	    
			if(check_mshr){
				in_mshr = true; // make decision on the basis of above line 
				gpgpu_prefetch_cancelled++; // stat
			}

			issue_prefetch =  (!in_mshr) && ((mf->mem_acc == GLOBAL_ACC_R) || (mf->mem_acc == LOCAL_ACC_R)) && (!check_me_prefetch);
	
		    // if not in mshr -- send prefetch request
		
			if(issue_prefetch) {
					issue_prefetch_request(to_be_prefetched[j], mf->nbytes_L1, mf->sid, mf->wid, mf->mem_acc, mf->pc);
			} // if
		    // dont know how to stop sending prefetch requests which may belong to constant and texture regions (TODO)
			to_be_prefetched[j] = -1;
			
			// prefetch_start_address =  prefetch_start_address +  stride_distance; // next cache line (useful if degree  > 1)

	    } // if
	  } // for
	  return 0;
}

void * check_in_demand_waiting_queue(unsigned long long int addr, int sid) {

		//check in already sent prefetches
		// check in sent queue
	    //printf("searching for = %x\n", addr);
		//printf("size = %d\n", prefetch_sent_requests[sid].size());
		for(int i=0; i < (int)prefetch_sent_requests[sid].size(); i++) {
				  //fflush(stdout);
				  //printf("damn thing = %x, coreid = %d\n", prefetch_sent_requests[sid].at(i)->addr, prefetch_sent_requests[sid].at(i)->sid );
				  //fflush(stdout);
				   if(prefetch_sent_requests[sid].at(i)->addr == addr) {
								    //printf("confirm:, address matched (sent) = %x\n", addr);
								if (prefetch_sent_requests[sid].at(i)->there_was_demand == true) {
									//printf("confirm:, there is demand for (sent) = %x\n", addr);
									return prefetch_sent_requests[sid].at(i);
								}
					}
		}

		for(int i=0; i < (int)prefetch_process_requests[sid].size(); i++) {
				  if(prefetch_process_requests[sid].at(i)->addr == addr) {
								//printf("confirm:, address matched (process) = %x\n", addr);
								if (prefetch_process_requests[sid].at(i)->there_was_demand == true) {
										//printf("confirm:, there is demand for (process) = %x\n", addr);
										return prefetch_process_requests[sid].at(i);
								 }
					}
		}

	    return NULL;
}
